//************************************************************************************************************
// CoNatural.Data.dll
// Author: Roger Torres
//************************************************************************************************************
// You can use, modify, and distribute the source code and executable programs based on the source code.
// This source code is provided "as is" and without warranties as to performance or merchantability. 
// The author and/or distributors of this source code may have made statements about this source code. 
// Any such statements do not constitute warranties and shall not be relied on by the user in deciding 
// whether to use this source code. This source code is provided without any express or implied warranties 
// whatsoever. Because of the diversity of conditions and hardware under which this source code may be used, 
// no warranty of fitness for a particular purpose is offered. The user is advised to test the source code 
// thoroughly before relying on it. The user must assume the entire risk of using the source code.
//************************************************************************************************************

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using CoNatural.Data.Binders;
#if !NET40
using CoNatural.Helpers;
#endif

namespace CoNatural.Data {
	/// <summary>
	/// Generic connection class.
	/// </summary>
	public class ConnectionBase : IConnection {
		/// <summary>
		/// Creates a new connection object
		/// </summary>
		/// <param name="connectionString">The connection string.</param>
		/// <param name="dataBinder">The data binder.</param>
		public ConnectionBase(string connectionString, IDataBinder dataBinder) {
			ConnectionString = connectionString;
			TimeOut = 30000; // get from config?
			DataBinder = dataBinder;
		}

		#region Properties

		public IDataBinder DataBinder { get; private set; }
		public string ConnectionString { get; private set; }
		public int TimeOut { get; set; }
		public CommandType ExecutionMode { get; set; }

		#endregion

		#region Command Execution

		public event EventHandler<CommandExecutionEventArgs> BeforeExecute;
		public event EventHandler<CommandExecutionEventArgs> AfterExecute;

		internal void RaiseBeforeExecuteEvent(IExecutionContext context) {
			if (BeforeExecute != null)
				BeforeExecute(this, new CommandExecutionEventArgs(context));
		}

		internal void RaiseAfterExecuteEvent(IExecutionContext context) {
			if (AfterExecute != null) {
				var args = new CommandExecutionEventArgs(context);
				AfterExecute(this, args);

				// filter dal exceptions
				if (args.IgnoreErrors)
					return;
			}

			// rethrow dal exceptions if not filtered by AfterExecute handlers
			if (context.Error != null)
				throw context.Error;
		}

		public int Execute(ICommand iCommand) {
			using (var ctx = new CommandExecutionContext(this, iCommand)) {
				try {
					ctx.Open();
					return ctx.ExecuteNonQuery();
				}
				catch (Exception ex) {
					ctx.HandleError(ex);
				}
			}
			return 0;
		}

		public T ExecuteScalar<T>(ICommand iCommand) {
			using (var ctx = new CommandExecutionContext(this, iCommand)) {
				try {
					ctx.Open();
					return ctx.ExecuteScalar<T>();
				}
				catch (Exception ex) {
					ctx.HandleError(ex);
				}
			}
			return default(T);
		}

		public T Execute<T>(ICommand iCommand) where T : class, new() {
			return Execute<T>(iCommand, null);
		}

		public T Execute<T>(ICommand iCommand, ReaderCallback<T> callback) where T : class, new() {
			using (var ctx = new ReaderExecutionContext<T>(this, iCommand)) {
				try {
					ctx.Open();
					return ctx.Execute(callback);
				}
				catch (Exception ex) {
					ctx.HandleError(ex);
				}
			}
			return default(T);
		}

		public IEnumerable<T> ExecuteReader<T>(ICommand iCommand) where T : class, new() {
			return ExecuteReader<T>(iCommand, null);
		}

		public IEnumerable<T> ExecuteReader<T>(ICommand iCommand, ReaderCallback<T> callback) where T : class, new() {
			using (var ctx = new ReaderExecutionContext<T>(this, iCommand)) {
				foreach (var instance in ctx.ExecuteReader(callback))
					yield return instance;
			}
		}

		public int ExecuteReader<T>(T target, ICommand iCommand) where T : class, new() {
			return ExecuteReader<T>(target, iCommand, null);
		}

		public int ExecuteReader<T>(T target, ICommand iCommand, ReaderCallback<T> callback) where T : class, new() {
			int records = 0;
			using (var ctx = new ReaderExecutionContext<T>(this, iCommand)) {
				try {
					ctx.Open();
					records = ctx.ExecuteReader(target, callback);
				}
				catch (Exception ex) {
					ctx.HandleError(ex);
				}
			}
			return records;
		}

		public int Execute(ICommand iCommand, object obj) {
			using (var ctx = new CommandExecutionContext(this, iCommand, obj)) {
				try {
					ctx.Open();
					return ctx.ExecuteNonQuery();
				}
				catch (Exception ex) {
					ctx.HandleError(ex);
				}
			}
			return 0;
		}

		public void Execute(params ICommand[] iCommands) {
			Execute(iCommands.AsEnumerable<ICommand>(), false);
		}

		public void Execute(ICommand iCommand, IEnumerable<object> objs, bool inBatch) {
			if (inBatch && ExecutionMode == CommandType.StoredProcedure) {
				using (var bctx = new BatchExecutionContext(this, "Batch of " + iCommand.GetType().Name, true)) {
					try {
						foreach (var o in objs)
							bctx.AddCommand(new CommandExecutionContext(this, iCommand, o));
						bctx.Open();
						bctx.Execute();
					}
					catch (Exception ex) {
						bctx.HandleError(ex);
					}
				}
			}
			else {
				foreach (var o in objs)
					Execute(iCommand, o);
			}
		}

		public void Execute(IEnumerable<ICommand> iCommands, bool inBatch) {
			if (inBatch && ExecutionMode == CommandType.StoredProcedure) {
				using (var bctx = new BatchExecutionContext(this, "Batch", false)) {
					try {
						foreach (var iCommand in iCommands)
							bctx.AddCommand(new CommandExecutionContext(this, iCommand));
						bctx.Open();
						bctx.Execute();
					}
					catch (Exception ex) {
						bctx.HandleError(ex);
					}
				}
			}
			else {
				foreach (var iCommand in iCommands)
					Execute(iCommand);
			}
		}

		public void Execute<T>(IEnumerable<T> objs, Func<T, Tuple<object, ICommand>> tupleBinder, bool inBatch) {
			if (inBatch && ExecutionMode == CommandType.StoredProcedure) {
				using (var bctx = new BatchExecutionContext(this, "Batch", false)) {
					try {
						bctx.AddCommands<T>(objs, tupleBinder);
						bctx.Open();
						bctx.Execute();
					}
					catch (Exception ex) {
						bctx.HandleError(ex);
					}
				}
			}
			else {
				foreach (var o in objs) {
					var tuple = tupleBinder(o);
					if (tuple == null)
						continue;
					Execute(tuple.Item2, tuple.Item1);
				}
			}
		}

		#endregion
	}
}
